/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.infinityk.tangentgenerator;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author Asier
 */
public class Mesh {
    public String name;
    public Triangle[] triangles;
    public Vec3[] vertices;
    public Vec3[] normals;
    public Vec2[] uv;
    public Vec3[] tangents;

    public Mesh() {
    }

    public Mesh(int nVertices, int nFaces) {
        triangles = new Triangle[nFaces];
        vertices = new Vec3[nVertices];
        normals = new Vec3[nVertices];
        uv = new Vec2[nVertices];
        tangents = new Vec3[nVertices];
    }

    public void parse(String content){
        ArrayList<Vec3> vv = new ArrayList<Vec3>();
        ArrayList<Vec2> vt = new ArrayList<Vec2>();
        ArrayList<Vec3> vn = new ArrayList<Vec3>();
        // vx_vy_vz_vu_vv_nx_ny_nz -> indexFinalVertex indexV indexT indexN
        HashMap<String,int[]> finalVertices = new HashMap<String, int[]>();
        ArrayList<int[]> tempTriangles = new ArrayList<int[]>();

        int currentIndex = 0;

        int firstIndex = 0;
        int lastIndex = content.indexOf("\n");
        while (lastIndex != -1){
            String line = content.substring(firstIndex,lastIndex).trim();
            String[] splitted = line.split(" ");
            if (splitted[0].toLowerCase().equals("v")){
                vv.add( new Vec3(Double.parseDouble(splitted[1]), 
                        Double.parseDouble(splitted[2]),
                        Double.parseDouble(splitted[3])));
            }else if (splitted[0].toLowerCase().equals("vt")){
                vt.add( new Vec2(Double.parseDouble(splitted[1]),
                        Double.parseDouble(splitted[2])));
            }else if (splitted[0].toLowerCase().equals("vn")){
                vn.add( new Vec3(Double.parseDouble(splitted[1]),
                        Double.parseDouble(splitted[2]),
                        Double.parseDouble(splitted[3])));
            }else if (splitted[0].toLowerCase().equals("f")){
                String[] v1 = splitted[1].split("/");
                String[] v2 = splitted[2].split("/");
                String[] v3 = splitted[3].split("/");

                int v1v = Integer.parseInt(v1[0]) - 1;
                int v1t = Integer.parseInt(v1[1]) - 1;
                int v1n = Integer.parseInt(v1[2]) - 1;
                String id1 = vv.get(v1v).x + "_" + vv.get(v1v).y + "_" + vv.get(v1v).z +
                        "_" + vt.get(v1t).x + "_" + vt.get(v1t).y +
                        "_" + vn.get(v1n).x + "_" + vn.get(v1n).y + "_" + vn.get(v1n).z;
                int v1i = -1;
                if (finalVertices.containsKey(id1)){
                    v1i = finalVertices.get(id1)[0];
                }else{
                    v1i = currentIndex; currentIndex++;
                    finalVertices.put(id1, new int[]{v1i, v1v, v1t, v1n});
                }

                int v2v = Integer.parseInt(v2[0]) - 1;
                int v2t = Integer.parseInt(v2[1]) - 1;
                int v2n = Integer.parseInt(v2[2]) - 1;
                String id2 = vv.get(v2v).x + "_" + vv.get(v2v).y + "_" + vv.get(v2v).z +
                        "_" + vt.get(v2t).x + "_" + vt.get(v2t).y +
                        "_" + vn.get(v2n).x + "_" + vn.get(v2n).y + "_" + vn.get(v2n).z;
                int v2i = -1;
                if (finalVertices.containsKey(id2)){
                    v2i = finalVertices.get(id2)[0];
                }else{
                    v2i = currentIndex; currentIndex++;
                    finalVertices.put(id2, new int[]{v2i, v2v, v2t, v2n});
                }

                int v3v = Integer.parseInt(v3[0]) - 1;
                int v3t = Integer.parseInt(v3[1]) - 1;
                int v3n = Integer.parseInt(v3[2]) - 1;
                String id3 = vv.get(v3v).x + "_" + vv.get(v3v).y + "_" + vv.get(v3v).z +
                        "_" + vt.get(v3t).x + "_" + vt.get(v3t).y +
                        "_" + vn.get(v3n).x + "_" + vn.get(v3n).y + "_" + vn.get(v3n).z;
                int v3i = -1;
                if (finalVertices.containsKey(id3)){
                    v3i = finalVertices.get(id3)[0];
                }else{
                    v3i = currentIndex; currentIndex++;
                    finalVertices.put(id3, new int[]{v3i, v3v, v3t, v3n});
                }

                tempTriangles.add(new int[]{v1i,v2i,v3i});
            }
            firstIndex = lastIndex + 1;
            lastIndex = content.indexOf("\n",firstIndex);
        }

       int nTriangles = tempTriangles.size();
       triangles = new Triangle[nTriangles];
       for(int i = 0; i < nTriangles; i++){
           triangles[i] = new Triangle(tempTriangles.get(i)[0], tempTriangles.get(i)[1], tempTriangles.get(i)[2]);
       }

       int nVertex = finalVertices.size();
       vertices = new Vec3[nVertex];
       normals = new Vec3[nVertex];
       uv = new Vec2[nVertex];
       tangents = new Vec3[nVertex];
       for(String key : finalVertices.keySet()){
           int[] tempVs = finalVertices.get(key);
           vertices[tempVs[0]] = new Vec3(vv.get(tempVs[1]).x, vv.get(tempVs[1]).y, vv.get(tempVs[1]).z);
           normals[tempVs[0]] = new Vec3(vn.get(tempVs[3]).x, vn.get(tempVs[3]).y, vn.get(tempVs[3]).z);
           uv[tempVs[0]] = new Vec2(vt.get(tempVs[2]).x, vt.get(tempVs[2]).y);
           tangents[tempVs[0]] = new Vec3(0,0,0);
       }
    }

    public byte[] serialize(boolean bigEndianMetada, boolean bigEndianData, boolean saveNormals, boolean saveTangents) throws IOException{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);

        //byte flag -> XXXX XXNT N = Normals; T = Tangents
        byte flag = 0x00;
        if (saveNormals) { flag |= 0x02; }
        if (saveTangents) { flag |= 0x01; }
        dos.writeByte(flag);

        //int nVertices -> number of vertices
        int nVertices = vertices.length;
        if(bigEndianMetada){
            dos.writeInt( swap(nVertices) );
        }else{
            dos.writeInt( nVertices );
        }
        
        //int nTriangles -> number of triangles
        int nTriangles = triangles.length;       
        if(bigEndianMetada){
            dos.writeInt( swap(nTriangles) );
        }else{
            dos.writeInt( nTriangles );
        }
        
        // foreach vertex
        for(int i = 0; i < nVertices; i++){
            // float x float y float z
            dos.writeFloat( (float) vertices[i].x );
            dos.writeFloat( (float) vertices[i].y );
            dos.writeFloat( (float) vertices[i].z );
            // float u float v
            dos.writeFloat( (float) uv[i].x );
            dos.writeFloat( (float) uv[i].y );

            // short nx ny nz
            short[] n = floatDirectionToShort(normals[i]);
            if(bigEndianData){
                dos.writeShort(n[0]);
                dos.writeShort(n[1]);
                dos.writeShort(n[2]);
            }else{
                dos.writeShort(swap(n[0]));
                dos.writeShort(swap(n[1]));
                dos.writeShort(swap(n[2]));
            }

            // short tx ty tz
            short[] t = floatDirectionToShort(tangents[i]);
            if(bigEndianData){
                dos.writeShort(t[0]);
                dos.writeShort(t[1]);
                dos.writeShort(t[2]);
            }else{
                dos.writeShort(swap(t[0]));
                dos.writeShort(swap(t[1]));
                dos.writeShort(swap(t[2]));
            }
            
        }

        // foreach triangle
        if (nTriangles > 0x0000FFFF){
            throw new IllegalArgumentException("Triangle count > 0x0000FFFF");
        }
        for(int i = 0; i < nTriangles; i++){
            if(bigEndianData){
                dos.writeByte( triangles[i].indices[0] >> 8 );
                dos.writeByte( triangles[i].indices[0] );
                dos.writeByte( triangles[i].indices[1] >> 8);
                dos.writeByte( triangles[i].indices[1] );
                dos.writeByte( triangles[i].indices[2] >> 8);
                dos.writeByte( triangles[i].indices[2] );
            }else{
                dos.writeByte( triangles[i].indices[0] );
                dos.writeByte( triangles[i].indices[0] >> 8 );
                dos.writeByte( triangles[i].indices[1] );
                dos.writeByte( triangles[i].indices[1] >> 8 );
                dos.writeByte( triangles[i].indices[2] );
                dos.writeByte( triangles[i].indices[2] >> 8 );
            }
        }



        dos.close();
        return bos.toByteArray();
    }


    public void generateTangents(){
        int nVert = vertices.length;
        int nPoly = triangles.length;

        Vec3[] tempTan= new Vec3[nVert];
        for(int i = 0; i < nVert; i++){
            tempTan[i] = new Vec3();
        }
       
        for (int i = 0; i < nPoly; i ++) {
             int i1 = triangles[i].indices[0];
             int i2 = triangles[i].indices[1];
             int i3 = triangles[i].indices[2];

             Vec3 v1 = vertices[i1];
             Vec3 v2 = vertices[i2];
             Vec3 v3 = vertices[i3];

             Vec2 w1 = uv[i1];
             Vec2 w2 = uv[i2];
             Vec2 w3 = uv[i3];

             double x1 = v2.x - v1.x;
             double x2 = v3.x - v1.x;
             double y1 = v2.y - v1.y;
             double y2 = v3.y - v1.y;
             double z1 = v2.z - v1.z;
             double z2 = v3.z - v1.z;

             double s1 = w2.x - w1.x;
             double s2 = w3.x - w1.x;
             double t1 = w2.y - w1.y;
             double t2 = w3.y - w1.y;

             double r = 1.0 / (s1 * t2 - s2 * t1);
             Vec3 sdir = new Vec3((t2*x1-t1*x2)*r,(t2*y1-t1*y2)*r,(t2*z1-t1*z2)*r);
            
             tempTan[i1].sum( sdir );
             tempTan[i2].sum( sdir );
             tempTan[i3].sum( sdir );
        }

        for (int i = 0; i < nVert; i++) {
             Vec3 n = normals[i].copy();
             Vec3 t = tempTan[i];

             n.mult( n.dot(t) );
             t.minus( n );
             t.normalize();

             tangents[i].x = t.x;
             tangents[i].y = t.y;
             tangents[i].z = t.z;
        }
    }

    public static short[] floatDirectionToShort(Vec3 a){
        double max = Math.max(Math.max(a.x, a.y), a.z);
        short[] forReturn = new short[3];
        if (max == 0.0){
            forReturn[0] = 0;
            forReturn[1] = 0;
            forReturn[2] = 0;
        }else{
            forReturn[0] = (short)(a.x * Short.MAX_VALUE / max);
            forReturn[1] = (short)(a.y * Short.MAX_VALUE / max);
            forReturn[2] = (short)(a.z * Short.MAX_VALUE / max);
        }

        return forReturn;
    }

    public static short swap(short value) {
        int b1 = value & 0xff;
        int b2 = (value >> 8) & 0xff;

        return (short) (b1 << 8 | b2);
    }


    public static int swap(int value) {
        int b1 = (value) & 0xff;
        int b2 = (value >> 8) & 0xff;
        int b3 = (value >> 16) & 0xff;
        int b4 = (value >> 24) & 0xff;

        return b1 << 24 | b2 << 16 | b3 << 8 | b4;
    }


}
